package com.lc1993929.tank.astar;

import com.lc1993929.tank.constants.Direction;
import com.lc1993929.tank.constants.MapElement;
import com.lc1993929.tank.entity.vo.DirectionAndDistance;
import com.lc1993929.tank.map.Coord;
import com.lc1993929.tank.map.GameMap;
import com.lc1993929.tank.map.Node;
import com.lc1993929.tank.util.MapUtil;

import java.util.*;

/**
 * ClassName: AStar
 *
 * @author kesar
 * @Description: A星算法
 */
public class AStar {


    public final static int DIRECT_VALUE = 1; // 横竖移动代价

    static Queue<Node> openList = new PriorityQueue<Node>(); // 优先队列(升序)
    static List<Node> closeList = new ArrayList<Node>();


    public static DirectionAndDistance ArmataFindNextStep(GameMap gameMap) {
        if (gameMap == null) {
            return DirectionAndDistance.builder().direction(Direction.WAIT).distance(0).build();
        }
        // clean
        openList.clear();
        closeList.clear();
        // 开始搜索
        openList.add(gameMap.start);
        DirectionAndDistance directionAndDistance = armataMoveNodes(gameMap);
        return directionAndDistance;
    }

    private static DirectionAndDistance armataMoveNodes(GameMap gameMap) {
        while (!openList.isEmpty()) {
            //如果终点坐标在close中，则绘制路径
            if (isCoordInClose(gameMap.end.coord)) {
                Map<Integer, Node> nextNodes = armataDrawPath(gameMap.maps, gameMap.end);
                return armataFindDirection(nextNodes, gameMap.start);
            }
            //弹栈出来的第一个是目前到起点和终点之和最小的点
            Node current = openList.poll();
            //将该点加入close中
            closeList.add(current);
            //将该点附近的点加入open中
            addNeighborNodeInOpen(gameMap, current);
        }
        //如果目标点是不可到达的，则找出离目标点最近的一个点前进
        Optional<Node> optional = closeList.stream().filter(node -> node.H > 0).min(Comparator.comparingInt(value -> value.H));
        if (!optional.isPresent()) {
            return DirectionAndDistance.builder().direction(MapUtil.getRandomDirection()).distance(2).build();
        }
        Node end = optional.get();
        Map<Integer, Node> nextNodes = armataDrawPath(gameMap.maps, end);
        return armataFindDirection(nextNodes, gameMap.start);
    }

    private static DirectionAndDistance armataFindDirection(Map<Integer, Node> nextNodes, Node start) {
        Direction direction = Direction.WAIT;
        Integer distance = 1;
        Node nextNode = nextNodes.get(1);
        if (nextNode.coord.x > start.coord.x) {
            direction = Direction.RIGHT;
        } else if (nextNode.coord.x < start.coord.x) {
            direction = Direction.LEFT;
        } else if (nextNode.coord.y > start.coord.y) {
            direction = Direction.DOWN;
        } else if (nextNode.coord.y < start.coord.y) {
            direction = Direction.UP;
        }
        if (nextNodes.size() > 1) {
            Node next2Node = nextNodes.get(2);
            if ((next2Node.coord.x == nextNode.coord.x && nextNode.coord.x == start.coord.x)
                    || (next2Node.coord.y == nextNode.coord.y && nextNode.coord.y == start.coord.y)) {
                distance = 2;
            }
        }
        return DirectionAndDistance.builder().direction(direction).distance(distance).build();
    }


    private static Map<Integer, Node> armataDrawPath(Map<Coord, MapElement> maps, Node end) {
        Map<Integer, Node> result = new HashMap<>();
        if (end == null || maps == null) {
            return null;
        }
        while (true) {
            if (end.parent.parent == null) {
                result.put(1, end);
                return result;
            }
            if (end.parent.parent.parent == null) {
                result.put(2, end);
                result.put(1, end.parent);
                return result;
            }
            //将终点坐标的父坐标变成路径
            end = end.parent;
        }
    }

    /**
     * 目标坐标
     */
    public static Coord findNextCoord(GameMap gameMap) {
        if (gameMap == null) {
            return null;
        }
        if (gameMap.start.coord.equals(gameMap.end.coord)) {
            return null;
        }
        // clean
        openList.clear();
        closeList.clear();
        // 开始搜索
        openList.add(gameMap.start);
        Node node = moveNextNode(gameMap);
        if (node == null) {
            return null;
        }
        return node.coord;
    }


    private static Node moveNextNode(GameMap gameMap) {
        while (!openList.isEmpty()) {
            //如果终点坐标在close中，则绘制路径
            if (isCoordInClose(gameMap.end.coord)) {
                return drawPath(gameMap.maps, gameMap.end);
            }
            //弹栈出来的第一个是目前到起点和终点之和最小的点
            Node current = openList.poll();
            //将该点加入close中
            closeList.add(current);
            //将该点附近的点加入open中
            addNeighborNodeInOpen(gameMap, current);
        }
        //如果目标点是不可到达的，则找出离目标点最近的一个点前进
        Optional<Node> optional = closeList.stream().filter(node -> node.H > 0).min(Comparator.comparingInt(value -> value.H));
        if (!optional.isPresent()) {
            return null;
        }
        Node end = optional.get();
        if (end.coord.equals(gameMap.start.coord)) {
            //如果起点被堵死了
            return null;
        }
        return drawPath(gameMap.maps, end);
    }

    /**
     * 开始算法
     */
    public static Direction findNextStep(GameMap gameMap) {
        if (gameMap == null) {
            return Direction.WAIT;
        }
        if (gameMap.start.coord.equals(gameMap.end.coord)) {
            return MapUtil.getRandomDirection();
        }
        // clean
        openList.clear();
        closeList.clear();
        // 开始搜索
        openList.add(gameMap.start);
        Direction direction = moveNodes(gameMap);
        return direction;
    }

    /**
     * 目标点能否到达
     */
    public static boolean canArrive(GameMap gameMap) {
        if (gameMap == null) {
            return false;
        }
        // clean
        openList.clear();
        closeList.clear();
        // 开始搜索
        openList.add(gameMap.start);
        return canMove(gameMap);
    }

    /**
     * 移动当前结点
     */
    private static boolean canMove(GameMap gameMap) {
        while (!openList.isEmpty()) {
            //如果终点坐标在close中，则可到达
            if (isCoordInClose(gameMap.end.coord)) {
                return true;
            }
            //弹栈出来的第一个是目前到起点和终点之和最小的点
            Node current = openList.poll();
            //将该点加入close中
            closeList.add(current);
            //将该点附近的点加入open中
            addNeighborNodeInOpen(gameMap, current);
        }
        return false;
    }

    /**
     * 移动当前结点
     */
    private static Direction moveNodes(GameMap gameMap) {
        while (!openList.isEmpty()) {
            //如果终点坐标在close中，则绘制路径
            if (isCoordInClose(gameMap.end.coord)) {
                Node nextNode = drawPath(gameMap.maps, gameMap.end);
                return findDirection(nextNode, gameMap.start);
            }
            //弹栈出来的第一个是目前到起点和终点之和最小的点
            Node current = openList.poll();
            //将该点加入close中
            closeList.add(current);
            //将该点附近的点加入open中
            addNeighborNodeInOpen(gameMap, current);
        }
        //如果目标点是不可到达的，则找出离目标点最近的一个点前进
        Optional<Node> optional = closeList.stream().filter(node -> node.H > 0).min(Comparator.comparingInt(value -> value.H));
        if (!optional.isPresent()) {
            return MapUtil.getRandomDirection();
        }
        Node end = optional.get();
        Node nextNode = drawPath(gameMap.maps, end);
        return findDirection(nextNode, gameMap.start);
    }

    /**
     * 根据起点和下一个目标节点选出前进方向
     *
     * @param nextNode
     * @param start
     * @return
     */
    public static Direction findDirection(Node nextNode, Node start) {
        if (nextNode.coord.x > start.coord.x) {
            return Direction.RIGHT;
        } else if (nextNode.coord.x < start.coord.x) {
            return Direction.LEFT;
        } else if (nextNode.coord.y > start.coord.y) {
            return Direction.DOWN;
        } else if (nextNode.coord.y < start.coord.y) {
            return Direction.UP;
        } else {
            return Direction.WAIT;
        }
    }

    /**
     * 在二维数组中绘制路径
     */
    private static Node drawPath(Map<Coord, MapElement> maps, Node end) {
        if (end == null || maps == null) {
            return null;
        }
        while (true) {
            if (end.parent.parent == null) {
                return end;
            }
            //将终点坐标的父坐标变成路径
            end = end.parent;
        }
    }

    /**
     * 添加所有邻结点到open表
     */
    private static void addNeighborNodeInOpen(GameMap gameMap, Node current) {
        int x = current.coord.x;
        int y = current.coord.y;

        // 左
        addNeighborNodeInOpen(gameMap, current, x - 1, y, DIRECT_VALUE);
        // 上
        addNeighborNodeInOpen(gameMap, current, x, y - 1, DIRECT_VALUE);
        // 右
        addNeighborNodeInOpen(gameMap, current, x + 1, y, DIRECT_VALUE);
        // 下
        addNeighborNodeInOpen(gameMap, current, x, y + 1, DIRECT_VALUE);
    }

    /**
     * 添加一个邻结点到open表
     */
    private static void addNeighborNodeInOpen(GameMap gameMap, Node current, int x, int y, Integer value) {
        //如果该点无法加入到open中，则直接忽略
        if (canAddNodeToOpen(gameMap, x, y)) {
            Node end = gameMap.end;
            Coord coord = new Coord(x, y);

            int G = current.G + value; // 计算邻结点的G值
            Node child = findNodeInOpen(coord);
            if (child == null) {
                int H = calcH(end.coord, coord); // 计算H值
                if (isEndNode(end.coord, coord)) {
                    child = end;
                    child.parent = current;
                    child.G = G;
                    child.H = H;
                } else {
                    child = new Node(coord, current, G, H);
                }
                openList.add(child);
            } else if (child.G > G) {
                child.G = G;
                child.parent = current;
                openList.add(child);
            }
        }
    }

    /**
     * 从Open列表中查找结点
     */
    private static Node findNodeInOpen(Coord coord) {
        if (coord == null || openList.isEmpty()) {
            return null;
        }
        for (Node node : openList) {
            if (node.coord.equals(coord)) {
                return node;
            }
        }
        return null;
    }

    /**
     * 计算H的估值：“曼哈顿”法，坐标分别取差值相加
     */
    public static int calcH(Coord end, Coord coord) {
        return Math.abs(end.x - coord.x)
                + Math.abs(end.y - coord.y);
    }

    /**
     * 判断结点是否是最终结点
     */
    private static boolean isEndNode(Coord end, Coord coord) {
        return end.equals(coord);
    }

    /**
     * 判断结点能否放入Open列表
     */
    private static boolean canAddNodeToOpen(GameMap gameMap, int x, int y) {
        // 是否在地图中
        if (x < 0 || x >= gameMap.width || y < 0 || y >= gameMap.hight) {
            return false;
        }
        // 判断是否是不可通过的结点
        MapElement targetNodeElement = gameMap.maps.get(new Coord(x, y));
        //如果节点类型是终点的节点类型，直接返回true
        if (targetNodeElement == gameMap.maps.get(gameMap.end.coord)) {
            return true;
        }
        if (targetNodeElement == MapElement.NPC
                || targetNodeElement == MapElement.BAR
                || targetNodeElement == MapElement.ENEMY_ARMATA1
                || targetNodeElement == MapElement.ENEMY_ARMATA2
                || targetNodeElement == MapElement.ENEMY_K2PANTHER
                || targetNodeElement == MapElement.ENEMY_T90
                || targetNodeElement == MapElement.ENEMY_WZ123
                || targetNodeElement == MapElement.MY_ARMATA1
                || targetNodeElement == MapElement.MY_ARMATA2
                || targetNodeElement == MapElement.MY_K2PANTHER
                || targetNodeElement == MapElement.MY_T90
                || targetNodeElement == MapElement.MY_WZ123
        ) {
            return false;
        }
        // 判断结点是否存在close表
        return !isCoordInClose(x, y);
    }

    /**
     * 判断坐标是否在close表中
     */
    private static boolean isCoordInClose(Coord coord) {
        return coord != null && isCoordInClose(coord.x, coord.y);
    }

    /**
     * 判断坐标是否在close表中
     */
    private static boolean isCoordInClose(int x, int y) {
        if (closeList.isEmpty()) {
            return false;
        }
        for (Node node : closeList) {
            if (node.coord.x == x && node.coord.y == y) {
                return true;
            }
        }
        return false;
    }

    //暂时不增加复活币的权重
    private int calcValue(Map<Coord, MapElement> maps, Coord coord) {

        // 根据目标点附近的坦克和当前点计算代价
        MapElement mapElement = maps.get(coord);
        if (mapElement == MapElement.WAY) {
            return 1;
        } else if (mapElement == MapElement.RESURGENCE) {
            return -5;
        } else if (mapElement == MapElement.MISTY) {
            return 1;
        }
        return 1;
    }

    /**
     * 避开敌方火力的下一步坐标
     */
    public static Coord findNextCoordVoidEnemyFire(GameMap gameMap) {
        if (gameMap == null) {
            return null;
        }
        if (gameMap.start.coord.equals(gameMap.end.coord)) {
            return null;
        }
        // clean
        openList.clear();
        closeList.clear();
        // 开始搜索
        openList.add(gameMap.start);
        Node node = moveNextNodeVoidEnemyFire(gameMap);
        if (node == null) {
            return null;
        }
        return node.coord;
    }

    private static Node moveNextNodeVoidEnemyFire(GameMap gameMap) {
        while (!openList.isEmpty()) {
            //如果终点坐标在close中，则绘制路径
            if (isCoordInClose(gameMap.end.coord)) {
                return drawPath(gameMap.maps, gameMap.end);
            }
            //弹栈出来的第一个是目前到起点和终点之和最小的点
            Node current = openList.poll();
            //将该点加入close中
            closeList.add(current);
            //将该点附近的点加入open中
            addNeighborNodeInOpenVoidEnemyFire(gameMap, current);
        }
        //如果目标点是不可到达的，则找出离目标点最近的一个点前进
        Optional<Node> optional = closeList.stream().filter(node -> node.H > 0).min(Comparator.comparingInt(value -> value.H));
        if (!optional.isPresent()) {
            return null;
        }
        Node end = optional.get();
        if (end.coord.equals(gameMap.start.coord)) {
            //如果起点被堵死了
            return null;
        }
        return drawPath(gameMap.maps, end);
    }

    private static void addNeighborNodeInOpenVoidEnemyFire(GameMap gameMap, Node current) {
        int x = current.coord.x;
        int y = current.coord.y;

        // 左
        addNeighborNodeInOpenVoidEnemyFire(gameMap, current, x - 1, y, DIRECT_VALUE);
        // 上
        addNeighborNodeInOpenVoidEnemyFire(gameMap, current, x, y - 1, DIRECT_VALUE);
        // 右
        addNeighborNodeInOpenVoidEnemyFire(gameMap, current, x + 1, y, DIRECT_VALUE);
        // 下
        addNeighborNodeInOpenVoidEnemyFire(gameMap, current, x, y + 1, DIRECT_VALUE);
    }

    /**
     * 添加一个邻结点到open表
     */
    private static void addNeighborNodeInOpenVoidEnemyFire(GameMap gameMap, Node current, int x, int y, Integer value) {
        //如果该点无法加入到open中，则直接忽略
        if (canAddNodeToOpenVoidEnemyFire(gameMap, x, y)) {
            Node end = gameMap.end;
            Coord coord = new Coord(x, y);

            int G = current.G + value; // 计算邻结点的G值
            Node child = findNodeInOpen(coord);
            if (child == null) {
                int H = calcH(end.coord, coord); // 计算H值
                if (isEndNode(end.coord, coord)) {
                    child = end;
                    child.parent = current;
                    child.G = G;
                    child.H = H;
                } else {
                    child = new Node(coord, current, G, H);
                }
                openList.add(child);
            } else if (child.G > G) {
                child.G = G;
                child.parent = current;
                openList.add(child);
            }
        }
    }

    private static boolean canAddNodeToOpenVoidEnemyFire(GameMap gameMap, int x, int y) {
        // 是否在地图中
        if (x < 0 || x >= gameMap.width || y < 0 || y >= gameMap.hight) {
            return false;
        }
        // 判断是否是不可通过的结点
        MapElement targetNodeElement = gameMap.maps.get(new Coord(x, y));
        if (targetNodeElement == MapElement.NPC
                || targetNodeElement == MapElement.BAR
                || targetNodeElement == MapElement.ENEMY_ARMATA1
                || targetNodeElement == MapElement.ENEMY_ARMATA2
                || targetNodeElement == MapElement.ENEMY_K2PANTHER
                || targetNodeElement == MapElement.ENEMY_T90
                || targetNodeElement == MapElement.ENEMY_WZ123
                || targetNodeElement == MapElement.MY_ARMATA1
                || targetNodeElement == MapElement.MY_ARMATA2
                || targetNodeElement == MapElement.MY_K2PANTHER
                || targetNodeElement == MapElement.MY_T90
                || targetNodeElement == MapElement.MY_WZ123
        ) {
            return false;
        }
        if (gameMap.enemyValueMap[x][y] > 0) {
            return false;
        }
        // 判断结点是否存在close表
        return !isCoordInClose(x, y);
    }

}
